package com.unciv

import com.badlogic.gdx.Application
import com.badlogic.gdx.Game
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.utils.Align
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.models.metadata.GameSettings
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.translations.Translations
import com.unciv.ui.LanguagePickerScreen
import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.WorldScreen
import java.util.*
import kotlin.concurrent.thread

class UncivGame(parameters: UncivGameParameters) : Game() {
    // we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters
    constructor(version: String) : this(UncivGameParameters(version, null))

    val version = parameters.version
    private val crashReportSender = parameters.crashReportSender
    val exitEvent = parameters.exitEvent
    val cancelDiscordEvent = parameters.cancelDiscordEvent
    val fontImplementation = parameters.fontImplementation
    val consoleMode = parameters.consoleMode

    lateinit var gameInfo: GameInfo
    fun isGameInfoInitialized() = this::gameInfo.isInitialized
    lateinit var settings: GameSettings
    lateinit var crashController: CrashController
    /**
     * This exists so that when debugging we can see the entire map.
     * Remember to turn this to false before commit and upload!
     */
    var viewEntireMapForDebug = false
    /** For when you need to test something in an advanced game and don't have time to faff around */
    val superchargedForDebug = false

    /** Simulate until this turn on the first "Next turn" button press.
     *  Does not update World View changes until finished.
     *  Set to 0 to disable.
     */
    val simulateUntilTurnForDebug: Int = 0

    /** Console log battles
     */
    val alertBattle = false

    lateinit var worldScreen: WorldScreen

    var music: Music? = null
    val musicLocation = "music/thatched-villagers.mp3"
    private var isSizeRestored = false
    var isInitialized = false


    val translations = Translations()

    override fun create() {
        Gdx.input.setCatchKey(Input.Keys.BACK, true)
        if (Gdx.app.type != Application.ApplicationType.Desktop) {
            viewEntireMapForDebug = false
        }
        Current = this


        // If this takes too long players, especially with older phones, get ANR problems.
        // Whatever needs graphics needs to be done on the main thread,
        // So it's basically a long set of deferred actions.
        settings = GameSaver.getGeneralSettings() // needed for the screen
        screen = LoadingScreen()

        Gdx.graphics.isContinuousRendering = settings.continuousRendering

        thread(name = "LoadJSON") {
            RulesetCache.loadRulesets(printOutput = true)
            translations.tryReadTranslationForCurrentLanguage()
            translations.loadPercentageCompleteOfLanguages()

            if (settings.userId.isEmpty()) { // assign permanent user id
                settings.userId = UUID.randomUUID().toString()
                settings.save()
            }

            // This stuff needs to run on the main thread because it needs the GL context
            Gdx.app.postRunnable {
                ImageGetter.ruleset = RulesetCache.getBaseRuleset() // so that we can enter the map editor without having to load a game first
                thread(name="Music") { startMusic() }
                restoreSize()

                if (settings.isFreshlyCreated) {
                    setScreen(LanguagePickerScreen())
                } else { setScreen(MainMenuScreen()) }
                isInitialized = true
            }
        }
        crashController = CrashController.Impl(crashReportSender)
    }

    fun restoreSize() {
        if (!isSizeRestored && Gdx.app.type == Application.ApplicationType.Desktop && settings.windowState.height>39 && settings.windowState.width>39) {
            isSizeRestored = true
            Gdx.graphics.setWindowedMode(settings.windowState.width, settings.windowState.height)
        }
    }


    fun loadGame(gameInfo: GameInfo) {
        this.gameInfo = gameInfo
        ImageGetter.ruleset = gameInfo.ruleSet
        Gdx.input.inputProcessor = null // Since we will set the world screen when we're ready,
                                        // This is to avoid ANRs when loading.
        ImageGetter.refreshAtlas()
        worldScreen = WorldScreen(gameInfo.getPlayerToViewAs())
        setWorldScreen()
    }

    fun loadGame(gameName: String) {
        loadGame(GameSaver.loadGameByName(gameName))
    }

    fun startMusic() {
        if (settings.musicVolume < 0.01) return

        val musicFile = Gdx.files.local(musicLocation)
        if (musicFile.exists()) {
            music = Gdx.audio.newMusic(musicFile)
            music!!.isLooping = true
            music!!.volume = 0.4f * settings.musicVolume
            music!!.play()
        }
    }

    fun setScreen(screen: CameraStageBaseScreen) {
        Gdx.input.inputProcessor = screen.stage
        super.setScreen(screen)
    }

    fun setWorldScreen() {
        if (screen != null && screen != worldScreen) screen.dispose()
        setScreen(worldScreen)
        worldScreen.shouldUpdate = true // This can set the screen to the policy picker or tech picker screen, so the input processor must come before
        Gdx.graphics.requestRendering()
    }

    // This is ALWAYS called after create() on Android - google "Android life cycle"
    override fun resume() {
        super.resume()
        if (!isInitialized) return // The stuff from Create() is still happening, so the main screen will load eventually
    }

    override fun pause() {
        if (this::gameInfo.isInitialized) GameSaver.autoSave(this.gameInfo)
        super.pause()
    }

    override fun resize(width: Int, height: Int) {
        screen.resize(width, height)
    }

    override fun dispose() {
        cancelDiscordEvent?.invoke()

        // Log still running threads (should be only this one and "DestroyJavaVM")
        val numThreads = Thread.activeCount()
        val threadList = Array(numThreads) { _ -> Thread() }
        Thread.enumerate(threadList)

        if (::gameInfo.isInitialized){
            val autoSaveThread = threadList.firstOrNull { it.name == "Autosave" }
            if (autoSaveThread != null && autoSaveThread.isAlive) {
                // auto save is already in progress (e.g. started by onPause() event)
                // let's allow it to finish and do not try to autosave second time
                autoSaveThread.join()
            } else
                GameSaver.autoSaveSingleThreaded(gameInfo)      // NO new thread
            settings.save()
        }

        threadList.filter { it !== Thread.currentThread() && it.name != "DestroyJavaVM"}.forEach {
            println ("    Thread ${it.name} still running in UncivGame.dispose().")
        }
    }

    companion object {
        lateinit var Current: UncivGame
        fun isCurrentInitialized() = this::Current.isInitialized
    }
}

class LoadingScreen:CameraStageBaseScreen() {
    init {
        val happinessImage = ImageGetter.getImage("StatIcons/Happiness")
        happinessImage.center(stage)
        happinessImage.setOrigin(Align.center)
        happinessImage.addAction(Actions.sequence(
                Actions.delay(1f),
                Actions.rotateBy(360f, 0.5f)))
        stage.addActor(happinessImage)
    }
}

